//------------------------------------------------------------------------------
// X2D Game Engine
// Copyright (C) 2006-2007 Janusz Kowalski
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

#include <cstdlib>
#include <cassert>
#include <cstdio>

#include "SDL.h"
#include "os.h"
#include "x2d_Map.h"
#include "x2d_ext.h"

// 1. Zamiana obiektów statycznych w dynamiczne
// 2. Pobranie wskaźników do kolejnych graczy
//  - wskaźniki na liście wskaźników
//  - pobieranie wskaźnika na zasadzie "weź nastepny wskaźnik"
// Gracze, którymi nikt nie steruje, nie pojawiają się na ekranie.
// Graczowi mówimy, czy ktoś nim steruje. 
// 1. Zamiana obiektów statycznych graczy w graczy. Obługa graczy, pozostawienie
// niektórych graczy na mapie.
// 2. Zamiana obiektów statycznych innych niż gracze w obiekty aktywne. 

namespace x2d {

// !! The main problem with the functions below is that they return value they
// read from the file instead of modifying the value passed by reference.

//Uint32 endian_swap(Uint32 value) {
//  return SDL_Swap32(value);
//}
//
//Uint16 endian_swap(Uint16 value) {
//  return SDL_Swap16(value);
//}
//
//Uint8 endian_swap(Uint8 value) {
//  return value;
//}

// Read/write data using the network (big endian) byte order.

// T should be: Sint8, Uint8, Sint16, Uint16, Sint32, Uint32
template <class T> T net_read(FILE *stream) {
  T value;
  fread(&value, sizeof(value), 1, stream);
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
  return endian_swap(value);
#else
  return value;
#endif
}

template <class T> void net_write(FILE *stream, T value) {
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
  value = endian_swap(value);
#endif
  fwrite(&value, sizeof(value), 1, stream);
}

// The same, but using PhysFSz

Map::Map(int w, int h) {
//  current_plane=1;
  show_guidelines=true;
  this->w=w; this->h=h;
  
  for(int i=0; i<MAP_D; i++) data[i]=0;
  newMap(w,h);
  
  // Wypełnij wszystko niebem
  for(int i=0; i<MAP_D; i++)
    for(int y=0; y<h; y++)
      for(int x=0; x<w; x++) {
        int tilen=FormId_Nothing;  // Domylne niebo
        data[i][y*w+x]=tilen;
      }
}

int Map::getVShift(int x, int y, int z) {
  AttrT attr = getAttr(x,y,z);
  if(attr & ATTR_SHD) return 16;
  if(attr & ATTR_SHU) return -16;
  return 0;
}

int Map::getHShift(int x, int y, int z) {
  AttrT attr = getAttr(x,y,z);
  if(attr & ATTR_SHR) return 16;
  if(attr & ATTR_SHL) return -16;
  return 0;
}

// Clear map and active objects.
void Map::clear() {
  for(int i=0; i<MAP_D; i++)
    for(int y=0; y<h; y++)
      for(int x=0; x<w; x++) {
        int tilen=FormId_Nothing;
        data[i][y*w+x]=tilen;
    }
}


// Alloc new map with given dimensions
void Map::newMap(int w, int h) {
  this->w=w;
  this->h=h;
  for(int i=0; i<MAP_D; i++) {
    assert(this->data[i]==0); // Nie alokuj zaalokowanej mapy.
    this->data[i]=(Uint32*)malloc(sizeof(Uint32)*w*h);
    assert(this->data[i]!=0);
  }
}


// Free previously allocated map
void Map::freeMap() {
  for(int i=0; i<MAP_D; i++) if(data[i]!=0) {
    free(data[i]);
    data[i]=0;
  }
}

Map::~Map() {
  freeMap();
}
    
// void Map::currentPlane(int n) {
//   if(n>=0 && n<N_MAP_LAYERS) current_plane=n;
// }

void Map::passiveEvent(int x, int y, int plane, int formID) {
  int mxid=get(x,y,plane);
}

#define XDIV32(a) (a>0?a/32:a/32-1)

bool Map::inside(int x, int y) const {
  if(x>=0 && x<w && y>=0 && y<h) return true;
  else return false;
}

void Map::set(int x, int y, int plane, MapFieldT formId) {
  if(!inside(x,y)) return;
  data[plane][y*w+x]=formId;
}

void Map::setForm(int x, int y, int z, int formId) {
  
}

void Map::setAttr(int x, int y, int z, AttrT attr) {
  Uint32 formId = getForm(x,y,z);
  set(x,y,z,formId | ((Uint32)attr<<24));
}

static const char *mapMagicNumber = "X2D/MAP";
static const int mapMagicNumberSize = 8;

// Save map to file.
void Map::save(const char *path) {
  FILE *file = fileOpenWrite(path);
  
  fwrite(mapMagicNumber, mapMagicNumberSize, 1, file);
  net_write<Uint8>(file, 4);  // Major version number.
  net_write<Uint8>(file, 0);  // Minor version number.
  net_write<Uint32>(file,0);  // CRC.
  
  long dataOffsetPos = ftell(file);
  net_write<Uint32>(file,0);
  
  int dataOffset = ftell(file);
  
  net_write<Uint16>(file, w);
  net_write<Uint16>(file, h);
  
  for(int plane=0; plane<MAP_D; plane++) {
    int last = 0, i=0, j=0;
    
    for(i=0; i<w*h;) {
      for(j=i+1; (j<w*h) && (j-i<255) && (data[plane][j]==data[plane][i]); j++);
      net_write<Uint8>(file, j-i);
      net_write<Uint32>(file, data[plane][i]);
      i=j;
    }
  }
  
  fseek(file, dataOffsetPos, SEEK_SET);
  net_write<Uint32>(file,dataOffset);
  
  fclose(file);
}

// Load map from file.
void Map::load(const char *path) {
  FILE *file = fileOpenRead(path);
  
  freeMap();
  
  // Determine the file format.
  char magicBuffer[mapMagicNumberSize];
  fread(magicBuffer, mapMagicNumberSize, 1, file);
  
  if(strncmp(magicBuffer, mapMagicNumber, mapMagicNumberSize)!=0) {
    fclose(file);
    throw "Map::load(...): File format not recognized.";
  }
  
  Uint8 majorVersionNumber = net_read<Uint8>(file);
  if(majorVersionNumber != 4) {
    fclose(file);
    throw "Map::load(...): Error: Unsupported map format version.";
  }
  
  net_read<Uint8>(file);
  net_read<Uint32>(file);
  Uint32 dataOffset = net_read<Uint32>(file);
  fseek(file, dataOffset, SEEK_SET);
  w = net_read<Uint16>(file);
  h = net_read<Uint16>(file);
  newMap(w,h);
    
    
  int n = MAP_D;
  for(int plane=0; plane<n; plane++) {
    int length = 0, formId = 0;
    for(int i=0; i<w*h; i++) {
      if(length==0) {
        length=net_read<Uint8>(file);
        formId=net_read<Uint32>(file);
      }
      data[plane][i]=formId;
      length--;
    }
  }
  
  fclose(file);
}

} /* namespace x2d */
